#include "stdafx.h"
#include "LuaScript.h"
#include "config/Def.h"
#include "platform/Device.h"
#include "debug/Debug.h"
#include <vector>
#include <map>
#include "ILuaCallback.h"
#include <assert.h>

using namespace unigame;

LuaScript::LuaScript(const char* name):
Component(E_LUA_CT),
m_ls(0),
m_name(0)
{
	int32u len = strlen(name) + 1;
	m_name = uni_new char[len];
	memcpy(m_name, name, len);
	m_ls = lua_open();
	assert(m_ls && "lua state is null");
	luaL_openlibs(m_ls);

	attachScript(name);
}

LuaScript::~LuaScript()
{
	for(std::map<std::string, ILuaCallback*>::iterator it = m_callbacks.begin(); it != m_callbacks.end(); ++it)
	{
		it->second->removeScript(this);
	}
	uni_delete_array(m_name);
	lua_close(m_ls);
	m_ls = 0;
}

void LuaScript::attachScript(const char* name)
{
	assert(m_ls && "lua script not be loaded before attach");
	char path[100];
	memset(path, 0, 100);
	sprintf(path, "%s%s", Device::currentDevice()->getResourcePath(), name);
	int32u ret = luaL_dofile(m_ls, path);
	if(ret != 0)
	{
		Debug::log("[lua error] load file %s error: %d\n%s", m_name, ret, lua_tostring(m_ls, -1));
	}
}

void LuaScript::update()
{
	callFunction("update");
}

void LuaScript::callFunction(const char* name, const char* input, const char* output, ...)
{
	va_list args;
	va_start(args, output);
	callFunction(name, input, output, args);
	va_end(args);
}

void LuaScript::callFunction(const char* name)
{
	callFunction(name, "", "", 0);
}

void LuaScript::callFunction(const char* name, const char* input, ...)
{
	va_list args;
	va_start(args, input);
	callFunction(name, input, "", args);
	va_end(args);
}

int32u LuaScript::computeLen(const char* text)
{
	int32u len = strlen(text);
	int32u count = 0;
	for(int32u i = 0; i < len; ++i)
	{
		if(text[i] != 'v' && text[i] != 'm')
		{
			++count;
		}
	}
	return count;
}

inline void LuaScript::getValue(lua_State* ls, int index, char* out)
{
	lua_pushvalue(m_ls, index);
	*out = lua_tostring(m_ls, -1)[0];
	lua_pop(m_ls, 1);
}

inline void LuaScript::getValue(lua_State* ls, int index, int* out)
{
	*out = lua_tointeger(m_ls, index);
}

inline void LuaScript::getValue(lua_State* ls, int index, short* out)
{
	*out = (short)lua_tointeger(m_ls, index);
}

inline void LuaScript::getValue(lua_State* ls, int index, double* out)
{
	*out = lua_tonumber(m_ls, index);
}

inline void LuaScript::getValue(lua_State* ls, int index, float* out)
{
	*out = (float)lua_tonumber(m_ls, index);
}

inline void LuaScript::getValue(lua_State* ls, int index, std::string* out)
{
	lua_pushvalue(m_ls, index);
	*out = lua_tostring(m_ls, -1);
	lua_pop(m_ls, 1);
}

inline void LuaScript::pushValue(lua_State* ls, char* in)
{
	std::string str(1, *in);
	lua_pushstring(m_ls, str.c_str());
}

inline void LuaScript::pushValue(lua_State* ls, int* in)
{
	lua_pushinteger(m_ls, *in);
}

inline void LuaScript::pushValue(lua_State* ls, short* in)
{
	lua_pushinteger(m_ls, *in);
}

inline void LuaScript::pushValue(lua_State* ls, double* in)
{
	lua_pushnumber(m_ls, *in);
}

inline void LuaScript::pushValue(lua_State* ls, float* in)
{
	lua_pushnumber(m_ls, *in);
}

inline void LuaScript::pushValue(lua_State* ls, std::string* in)
{
	lua_pushstring(m_ls, in->c_str());
}

inline void LuaScript::createTable()
{
	lua_newtable(m_ls);
}

inline void LuaScript::putInTableVector(int32u tableSp, int32u index)
{
	lua_rawseti(m_ls, tableSp, index);
}

inline void LuaScript::putInTableMap(int32u tableSp)
{
	lua_rawset(m_ls, tableSp);
}

void LuaScript::getCallbackName(std::string* out)
{
	getValue(m_ls, 1, out);
}

//////////////////////////////
//i:int
//h:short
//c:char
//f:float
//d:double
//s:string, must pass string*
//v:prefix,vector, before i,d,s,c,h,f
//m:prefix,map, use string as key, berfore i,d,s,c,h,f
//don't nest vector nor map
//////////////////////////////
void LuaScript::callFunction(const char* name, const char* input, const char* output, va_list args)
{
	int32u inLen = computeLen(input);
	int32u outLen = computeLen(output);
	lua_getglobal(m_ls, name);
	for(int32u i = 0, j = 0; i < inLen; ++i, ++j)
	{
		switch(input[j])
		{
		case 'h':
		case 'i':
			pushValue(m_ls, &va_arg(args, int));
			break;
		case 'f':
		case 'd':
			pushValue(m_ls, &va_arg(args, double));
			break;
		case 'c':
			{
				char c = (char)va_arg(args, int);
				pushValue(m_ls, &c);
			}
			break;
		case 's':
			pushValue(m_ls, va_arg(args, std::string*));
			break;
		case 'v':
			++j;
			switch(input[j])
			{
			case 'i':
				args = makeInputVector<int>(args);
				break;
			case 'h':
				args = makeInputVector<short>(args);
				break;
			case 'd':
				args = makeInputVector<double>(args);
				break;
			case 'f':
				args = makeInputVector<float>(args);
				break;
			case 'c':
				args = makeInputVector<char>(args);
				break;
			case 's':
				args = makeInputVector<std::string>(args);
				break;
			default:
				break;
			}
			break;
		case 'm':
			++j;
			switch(input[j])
			{
			case 'i':
				args = makeInputMap<int>(args);
				break;
			case 'h':
				args = makeInputMap<short>(args);
				break;
			case 'd':
				args = makeInputMap<double>(args);
				break;
			case 'f':
				args = makeInputMap<float>(args);
				break;
			case 'c':
				args = makeInputMap<char>(args);
				break;
			case 's':
				args = makeInputMap<std::string>(args);
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
	}
	int32u ret = lua_pcall(m_ls, inLen, outLen, 0);
	if(ret != 0)
	{
		Debug::log("[lua error] call function %s error:%d\n%s", name, ret, lua_tostring(m_ls, -1));
		va_end(args);
		return;
	}
	makeOutput(outLen, output, args);
	lua_settop(m_ls, 0);
	va_end(args);
}

void LuaScript::makeOutput(int32u len, const char* format, va_list args)
{
	for(int32u i = 0, j = 0; i < len; ++i, ++j)
	{
		switch(format[j])
		{
		case 'h':
			getValue(m_ls, i - len, va_arg(args, short*));
			break;
		case 'i':
			getValue(m_ls, i - len, va_arg(args, int*));
			break;
		case 'f':
			getValue(m_ls, i - len, va_arg(args, float*));
			break;
		case 'd':
			getValue(m_ls, i - len, va_arg(args, double*));
			break;
		case 'c':
			getValue(m_ls, i - len, va_arg(args, char*));
			break;
		case 's':
			getValue(m_ls, i - len, va_arg(args, std::string*));
			break;
		case 'v':
			++j;
			switch(format[j])
			{
			case 'i':
				args = makeOutputVector<int>(args, i - len);
				break;
			case 'h':
				args = makeOutputVector<short>(args, i - len);
				break;
			case 'd':
				args = makeOutputVector<double>(args, i - len);
				break;
			case 'f':
				args = makeOutputVector<float>(args, i - len);
				break;
			case 'c':
				args = makeOutputVector<char>(args, i - len);
				break;
			case 's':
				args = makeOutputVector<std::string>(args, i - len);
				break;
			default:
				break;
			}
			break;
		case 'm':
			++j;
			switch(format[j])
			{
			case 'i':
				args = makeOutputMap<int>(args, i - len);
				break;
			case 'h':
				args = makeOutputMap<short>(args, i - len);
				break;
			case 'd':
				args = makeOutputMap<double>(args, i - len);
				break;
			case 'f':
				args = makeOutputMap<float>(args, i - len);
				break;
			case 'c':
				args = makeOutputMap<char>(args, i - len);
				break;
			case 's':
				args = makeOutputMap<std::string>(args, i - len);
				break;
			default:
				break;
			}
			break;
		default:
			break;
		}
	}
}

void LuaScript::getMessageParam(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	int32u len = computeLen(format);
	makeOutput(len, format, args);
	va_end(args);
}

void LuaScript::registerCallback(ILuaCallback* callback)
{
#if UNI_DEBUG
	std::map<std::string, ILuaCallback*>::iterator it = m_callbacks.find(callback->name());
	if(it != m_callbacks.end())
	{
		Debug::log("duplicate callback %s\n", callback->name());
	}
#endif
	m_callbacks[callback->name()] = callback;
	callback->addScript(this);
}

void LuaScript::removeCallback(const char* name)
{
	std::map<std::string, ILuaCallback*>::iterator it = m_callbacks.find(name);
	if(it != m_callbacks.end())
	{
		m_callbacks.erase(it);
	}
}

void LuaScript::runCallback(const char* name)
{
	std::map<std::string, ILuaCallback*>::iterator it = m_callbacks.find(name);
	assert(it != m_callbacks.end() && "can't find callback ");
	it->second->onCall(this);
}